// 【编辑相关】
import {
  isObject,
  isSame,
  clone,
  merge,
  ROOT,
  genMutations
} from 'boot/utils'

// 新坐标点
const NEW_COORD = {
  x: 0,
  y: 0
}

// 可自动合并操作记录的动作：{ 动作名: 合并方式（1-替换/2-合并/3-多项合并） }
const MERGABLE_ACTIONS = {}

// ----------------------------------------------------------------------------【utils】

// 是否不要记录操作（>0表示不记录）
let doNotRecord = 0

// 下一操作是否强制不合并
let forceNoMerge = false

// 生成转JSON对象或字符串的函数
// - @prop 附加属性名(若提供，则返回字符串为{ "prop": ... }格式）
// - @getData 获取数据的函数
// - @return 转换函数
// 返回函数参数：
// - @indent 行缩进（null表示返回原始对象）
function genToJson (getData, prop) {
  return indent => {
    const data = getData()
    if (indent == null) return data
    return JSON.stringify(prop != null ? { [prop]: data } : data, null, indent)
  }
}

// 自动生成撤销重做的动作
// - @action 动作名
// - @undoAction 撤销动作名（未提供则取action）
function genUndoRedo (action, undoAction) {
  return {
    [action + 'Undo']: async function ({ dispatch }, data) {
      await dispatch(undoAction || action, data[0])
    },
    [action + 'Redo']: async function ({ dispatch }, data) {
      await dispatch(action, data[1])
    }
  }
}

// 自动生成更新动作
// - @action 动作名
// - @name 要更新的状态属性名
// - @keyName 列表项标识字段名（若指定，则按列表方式更新）
// - @subName 子列表属性名（若指定，则更新列表项子项）
// 动作参数格式为：
// - @change 更新信息（子项更新时格式为：[列表项标识, 子项序号, 子项更新信息]）
// - @return 原信息（找不到时为null，无需更新时为undefined，子项更新时为：[列表项标识, 子项序号, 原子项信息]）
function genUpdateAction (action, name, keyName, subName) {
  const noRecord = action.startsWith('_')
  return {
    [action]: async function ({ state, commit, dispatch }, change) {
      let info = state[name]
      let key, index, subIndex
      if (keyName) {
        key = subName ? change[0] : change[keyName]
        index = info.findIndex(i => i[keyName] === key)
        if (index < 0) return null
        info = info[index]
      }
      if (subName) {
        subIndex = change[1]
        info = info[subName][subIndex]
        if (!info) return null
        change = change[2]
      }
      let old = {}
      Object.keys(change).forEach(i => {
        old[i] = info[i]
      })
      if (isSame(old, change)) return
      if (subName) {
        commit(name + 'Change', [index, [subName, subIndex, change, 2], 3])
        old = [key, subIndex, old]
        change = [key, subIndex, change]
      } else {
        commit(name + 'Change', keyName ? [index, change, 2] : change)
      }
      if (!noRecord) {
        await dispatch('record', { [action]: [old, change] })
      }
      return old
    },
    ...(noRecord ? {} : genUndoRedo(action))
  }
}

// 自动生成对象更新动作
// - @action 动作名
// - @name 要更新的对象状态属性名（支持Map类型）
// - @mode 更新模式：0-属性值整体替换（默认）/1-属性值按子属性单独更新/2-仅更新子属性（属性必须存在）
// 动作参数格式为：
// - @change 更新信息（属性值为null表示删除该属性，未找到的属性则自动添加）
// - @return 原信息（无需更新时为undefined）
function genObjectUpdateAction (action, name, mode) {
  const noRecord = action.startsWith('_')
  return {
    [action]: async function ({ state, commit, dispatch }, change) {
      const info = state[name]
      const isMap = info instanceof Map
      const old = {}
      Object.keys(change).forEach(i => {
        const oldVal = isMap ? info.get(i) : info[i]
        if (mode === 2 && !oldVal) return
        const newVal = change[i]
        if (
          mode === 2 ||
          (mode === 1 && isObject(oldVal) && isObject(newVal))
        ) {
          const oldSub = {}
          Object.keys(newVal).forEach(j => {
            oldSub[j] = oldVal[j] != null ? oldVal[j] : null
          })
          old[i] = oldSub
        } else {
          old[i] = oldVal != null ? oldVal : null
        }
      })
      if (isSame(old, change)) return
      commit(name + 'Change', change)
      if (!noRecord) {
        await dispatch('record', { [action]: [old, change] })
      }
      return old
    },
    ...(noRecord ? {} : genUndoRedo(action))
  }
}

console.log([genToJson, genUpdateAction, genObjectUpdateAction])

// ----------------------------------------------------------------------------【state】
const state = () => ({
  viewWidth: 100, // 当前编辑区视图大小
  viewHeight: 100,
  viewCenter: clone(NEW_COORD), // 当前编辑区视图中心位置坐标
  viewAim: null, // 视图中心要对准的位置坐标
  pointerPos: null, // 当前鼠标指向坐标

  undoStack: [], // 撤销记录栈，每项为：{ 动作名: 操作数据 }
  redoStack: [], // 重做记录栈，同上
  savedOper: null // 上次保存时的操作记录（用于恢复文件未修改状态）
})

// ----------------------------------------------------------------------------【getters】
const getters = {
  // 是否可撤销
  canUndo (state) {
    return state.undoStack.length > 0
  },

  // 是否可重做
  canRedo (state) {
    return state.redoStack.length > 0
  }
}

// ----------------------------------------------------------------------------【mutations】
const mutations = {
  ...genMutations([
    'viewWidth',
    'viewHeight',
    'viewCenter',
    'viewAim',
    'pointerPos',
    'undoStack',
    'redoStack',
    'savedOper'
  ]),

  // 压入撤销记录
  // - @oper 操作记录
  pushUndo (state, oper) {
    Object.freeze(oper) // 冻结操作记录，以避免被加入不必要的响应式处理（会造成卡顿）
    state.undoStack.push(oper)
  },

  // 弹出撤销记录
  popUndo (state) {
    state.undoStack.pop()
  },

  // 更新撤销记录
  // - @info 更新信息，若为数组则表示：[ 新操作数据, 旧操作数据（可选）]，若为函数则格式为：旧操作记录 => 新操作记录
  updateUndo (state, info) {
    const oper = state.undoStack[state.undoStack.length - 1]
    if (info instanceof Array) {
      const operData = Object.values(oper)[0]
      const [newData, oldData] = info
      operData[1] = newData
      if (oldData === undefined) return // oldData为undefined表示无需更新
      operData[0] = oldData
    } else if (typeof info === 'function') {
      state.undoStack[state.undoStack.length - 1] = Object.freeze(info(oper))
    }
  },

  // 限制撤销记录数量
  // - @limit 最大数量
  limitUndo (state, limit) {
    const excess = state.undoStack.length - limit
    if (excess > 0) {
      state.undoStack.splice(0, excess)
    }
  },

  // 压入重做记录
  // - @oper 操作记录
  pushRedo (state, oper) {
    Object.freeze(oper)
    state.redoStack.push(oper)
  },

  // 弹出重做记录
  popRedo (state) {
    state.redoStack.pop()
  }
}

// ----------------------------------------------------------------------------【actions】
const actions = {
  // 重置内容
  // - @info 内容信息
  async reset ({ getters, commit, dispatch }, info) {
    // ...
  },

  // 记录操作
  // - @oper 操作项：{ 动作名: 操作数据 }
  async record ({ state, commit, rootState }, oper) {
    if (doNotRecord) return

    // 若为可合并操作记录的动作，则判断上一操作是否为同一动作，且操作数据相同，都相同则进行合并
    let merged = false
    const action = Object.keys(oper)[0]
    const mergeMode = MERGABLE_ACTIONS[action]
    if (mergeMode) {
      if (forceNoMerge) {
        forceNoMerge = false
      } else if (
        state.undoStack.length > 0 &&
        state.redoStack.length === 0 &&
        rootState.file.modified
      ) {
        const lastData = state.undoStack[state.undoStack.length - 1][action]
        if (lastData) {
          const operData = oper[action]
          let newData, oldData
          switch (mergeMode) {
            case 1: // 替换模式下，前后数据相同则可合并
              if (isSame(lastData[1], operData[0])) {
                merged = true
                newData = clone(operData[1])
              }
              break
            case 2: // 合并模式下，数组为连接，对象为叠加，新旧顺序相反
              merged = true
              newData = merge(lastData[1], clone(operData[1]))
              oldData = merge(clone(operData[0]), lastData[0])
              break
            case 3: // 各项分别进行合并，合并规则同上
              merged = true
              newData = lastData[1].map((data, i) =>
                merge(data, clone(operData[1][i]))
              )
              oldData = lastData[0].map((data, i) =>
                merge(clone(operData[0][i]), data)
              )
              break
          }
          merged && commit('updateUndo', [newData, oldData])
        }
      }
    }

    // 不能合并则添加新记录
    if (!merged) {
      commit('pushUndo', clone(oper)) // 克隆一份，避免外部修改影响到记录
      commit('limitUndo', rootState.main.maxUndos)
      commit('redoStack', [])
    }

    commit('file/modified', true, ROOT)
  },

  // 清空操作记录
  async clearRecords ({ commit }) {
    commit('undoStack', [])
    commit('redoStack', [])
  },

  // 撤销
  async undo ({ state, commit, dispatch }) {
    let oper = state.undoStack[state.undoStack.length - 1]
    if (!oper) return
    commit('popUndo')
    commit('pushRedo', oper)

    ++doNotRecord
    oper = clone(oper) // 克隆一份来恢复，避免外部修改影响到记录
    for (const action in oper) {
      await dispatch(action + 'Undo', oper[action])
    }
    --doNotRecord

    oper = state.undoStack[state.undoStack.length - 1]
    commit('file/modified', !(oper && oper === state.savedOper), ROOT)
  },

  // 重做
  async redo ({ state, commit, dispatch }) {
    let oper = state.redoStack[state.redoStack.length - 1]
    if (!oper) return
    commit('popRedo')
    commit('pushUndo', oper)

    ++doNotRecord
    oper = clone(oper) // 克隆一份来恢复，避免外部修改影响到记录
    for (const action in oper) {
      await dispatch(action + 'Redo', oper[action])
    }
    --doNotRecord

    oper = state.undoStack[state.undoStack.length - 1]
    commit('file/modified', !(oper && oper === state.savedOper), ROOT)
  }
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}
